Skip to content

feat(signing): replace HMAC envelope with ed25519 detached signature#125

Merged
luofang34 merged 1 commit intomainfrom
pr/0.1.5-ed25519-replace-hmac
May 2, 2026
Merged

feat(signing): replace HMAC envelope with ed25519 detached signature#125
luofang34 merged 1 commit intomainfrom
pr/0.1.5-ed25519-replace-hmac

Conversation

@luofang34
Copy link
Copy Markdown
Owner

@luofang34 luofang34 commented May 2, 2026

Summary

Pre-0.1.5 used HMAC-SHA256 over the (SHA256SUMS, index.json)
envelope and called it a "signature" — a misnomer. HMAC is a Message
Authentication Code (FIPS 198-1); the verifier needs the same shared
secret as the signer, so it cannot provide non-repudiation and cannot
satisfy cross-organizational provenance expectations.

This PR keeps the envelope shape and replaces the cryptographic
primitive with ed25519. The BUNDLE.sig filename, the
sign_bundle / verify_bundle_signature API names, and the SIGN_*
diagnostic codes are now legitimate.

Three-layer integrity model (now documented in SYS-001 + README + QUALIFICATION)

  1. Content layersha256sum -c SHA256SUMS, no key required
  2. Metadata layer — verify with 32-byte ed25519 public key
  3. Provenance layer — non-repudiation via the asymmetric primitive

What changed

  • crates/evidence-core/src/bundle/signing.rs: ed25519 sign + verify, key file IO helpers
  • crates/evidence-core/src/lib.rs: re-export SigningKey, VerifyingKey, Signature, key helpers
  • cargo-evidence CLI: --signing-key/--verify-key paths now read 32-byte hex files (was raw HMAC secret bytes)
  • 1 diagnostic code renamed: VERIFY_HMAC_FAILUREVERIFY_SIGNATURE_INVALID (total code count unchanged)
  • Workspace deps: +ed25519-dalek (rand_core, zeroize), +rand_core; -hmac
  • SYS-001, HLR-015, LLR-015 descriptions updated to match the ed25519 reality
  • README adds a "Bundle integrity layers" section with verify/generate recipes
  • cert/QUALIFICATION.md adds an "Integrity layers" section pointing at the relevant DO-178C §7 SCM language and FIPS 198-1's HMAC-vs-signature distinction
  • evidence-core/test_count floor bumped 360 → 367 (seven new ed25519 unit tests in signing.rs)

What's deferred (PR-C, separate)

  • cargo evidence keygen subcommand with refuse-overwrite + --rotate
  • Default cert/signing.{key,pub} path resolution
  • signing.pub anchor consistency check at generate (refuses if local pubkey doesn't match the signing key just used)
  • Project-self keypair bootstrap + commit cert/signing.pub
  • CI signing-key handling docs

For now, README documents an openssl keygen recipe; the CLI takes explicit --signing-key / --verify-key paths.

Standards alignment

  • DO-178C §7 SCM requires "protection against unauthorized changes" but mandates no specific cryptographic primitive. ed25519 is strictly stronger than HMAC for this layer (covers all of HMAC's checks plus non-repudiation).
  • FIPS 198-1 explicitly distinguishes HMAC (symmetric, no non-repudiation) from digital signatures.
  • SLSA L2+ requires "digital signature" for build provenance; HMAC is not accepted by the SLSA / in-toto / DSSE chain. ed25519 satisfies the primitive requirement (a future SLSA L2 attestation wrapper is a separate roadmap item).

Override-Deterministic-Baseline

Override-Deterministic-Baseline: ed25519 swap rotates Cargo.lock
(adds ed25519-dalek + rand_core + curve25519-dalek transitive deps,
removes hmac), which rotates cargo_lock_hash in
deterministic-manifest.json. The deterministic_hash projection
legitimately drifts from the prior main baseline.

Test plan

  • cargo fmt --check
  • cargo clippy --workspace --all-targets -D warnings
  • cargo test --workspace --all-targets (all green; +7 new ed25519 unit tests)
  • RUSTDOCFLAGS='-D missing_docs -D rustdoc::broken_intra_doc_links' cargo doc --workspace --no-deps
  • cargo evidence trace --validate (TRACE_OK)
  • cargo evidence floors --format=jsonl (FLOORS_OK, 13 dimensions)
  • cargo evidence doctor (all checks green)
  • CI green on push (cross-host + Nix flavors; cross-time-determinism uses Override line above)

🤖 Generated with Claude Code

The pre-0.1.5 HMAC-SHA256 envelope over `(SHA256SUMS, index.json)`
was correct for in-organization integrity, but the API/docs called
it a "signature" — a misnomer. HMAC is a Message Authentication Code
(FIPS 198-1): the verifier needs the same shared secret as the
signer, so it cannot provide non-repudiation and cannot satisfy
cross-organizational provenance expectations (DO-178C §7 SCM
"protection against unauthorized changes" allows it; DO-178C
supplier→authority handoffs and SLSA L2+ build provenance do not).

This PR replaces the cryptographic primitive with ed25519 while
preserving the envelope shape, the `BUNDLE.sig` filename (now
legitimate), the `sign_bundle` / `verify_bundle_signature` API
names, and the `SIGN_*` diagnostic codes (now accurate).
`VERIFY_HMAC_FAILURE` becomes `VERIFY_SIGNATURE_INVALID` (the only
diagnostic-code rename); the total code count is unchanged.

Layered integrity model now documented (SYS-001, README, QUALIFICATION):

  1. Content layer    — sha256sum -c SHA256SUMS, no key required
  2. Metadata layer   — verify with 32-byte ed25519 public key
  3. Provenance layer — non-repudiation via the asymmetric primitive

CLI continues to take a key path; the file format is now 64 chars of
hex (32 raw bytes) instead of arbitrary HMAC secret bytes.
`cargo evidence verify --verify-key cert/signing.pub` and
`cargo evidence generate --signing-key cert/signing.key`. Default
path resolution + a native `cargo evidence keygen` subcommand land
in a follow-up PR-C; for now the README documents an `openssl`
recipe.

Workspace dep delta: +ed25519-dalek (with rand_core, zeroize),
+rand_core; -hmac. Pure-Rust, no FFI, matches the project's
"Offline-capable" invariant.

cargo-evidence/test_count floor bumped 142 → 142 (no change after
verify.rs split); evidence-core/test_count floor bumped 360 → 367
to absorb the seven new ed25519 unit tests in signing.rs.

Override-Deterministic-Baseline: ed25519 swap rotates Cargo.lock
(adds ed25519-dalek + rand_core + curve25519-dalek transitive deps,
removes hmac), which rotates `cargo_lock_hash` in
deterministic-manifest.json. The deterministic_hash projection
legitimately drifts from the prior main baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@luofang34 luofang34 merged commit 2331930 into main May 2, 2026
16 checks passed
@luofang34 luofang34 deleted the pr/0.1.5-ed25519-replace-hmac branch May 2, 2026 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants